[Kinesis Video Streams] OpenCVのビデオソースにGStreamerを使用してみました。
1 はじめに
CX事業本部の平内(SIN)です。
MLなどを扱う時、OpenCVによる画像処理が併せて利用される場面があるようです。
Kinesis Video Streamsでは、通常、エッジデバイスからの送信をGStreamerのシンクから行っています。今回は、ビデオ入力をOpenCVで処理し、それをGStreamerで扱うことをイメージして、OpenVCとGStreamerの統合を試してみました。
2 環境
作業した環境は、RaspberryPiです。
Raspberry Piは、Model 4B(メモリ4G)で、OSは、昨年9月の最新版(Raspbian GNU/Linux 10 (buster) 2019-09-26-raspbian-buster-full.img です。
$ cat /proc/cpuinfo | grep Revision Revision : c03112 $ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster $ uname -a Linux raspberrypi 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux
3 OpenCVの構築
通常配布されている、OpenCVのバイナリは、入力ソースが、FFMPEG、V4L2あたりになっています。(GStreamerは、NOになっている)
$ python (略) >>> import cv2 >>> print(cv2.getBuildInformation()) General configuration for OpenCV 3.2.0 ===================================== (略) Video I/O: DC1394 1.x: NO DC1394 2.x: YES (ver 2.2.5) FFMPEG: YES avcodec: YES (ver 58.35.100) avformat: YES (ver 58.20.100) avutil: YES (ver 56.22.100) swscale: YES (ver 5.3.100) avresample: YES (ver 4.0.0) GStreamer: NO V4L/V4L2: NO/YES gPhoto2: YES (略)
GStreamerを入出力に使用するためには、再構築の必要があります。
(1) コンパイルの環境について
ディスク
最近のRaspbianは、初回起動時に、自動的にディスクの拡張が行われますので、16G以上のSDカードなら容量は気にする必要はありません。
メモリ
下記は、make中にfreeコマンドで確認しているようですが、使用メモリが1.1Gを超えてきています。
$ free -m total used free shared buff/cache available Mem: 3906 1123 2493 8 289 2651 Swap: 99 0 99
今回使用したRasPi 4Bは、搭載メモリが4Gなので、何も問題は無かったのですが、Raspbianの配布イメージのデフォルトのスワップサイズは、100Mになっていますので、Model 3B など、搭載メモリが1Gの場合は、メモリ不足でmakeが途中でエラーとなります。
この場合、/etc/dphys-swapfileを編集して、サイズを上げて下さい。1G程度あれば充分なはずです。(最悪、不足した場合は、その時点で上げて、続きからmakeすればいいでしょう)
- /etc/dphys-swapfileを1Gに編集
$ sudo vi /etc/dphys-swapfile #CONF_SWAPSIZE=100 CONF_SWAPSIZE=1024
- SWAPのリスタート
$ sudo /etc/init.d/dphys-swapfile restart
- 確認
$ swapon NAME TYPE SIZE USED PRIO /var/swap file 1024M 0B -2
(2) gstreamerのインストール
OpenCVのコンパイル前に、GStreamerを入れておく必要があります。
$ sudo apt install cmake libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-tools libgtk2.0-dev gstreamer1.0-omx=1.0.0.1-0+rpi12+jessiepmg gstreamer1.0-plugins-bad gstreamer1.0-plugins-good
(3) OpenCV のダウンロード
一番新しそうな、4.2.0を入れてみました。
$ mkdir OpenCV && cd OpenCV $ wget https://github.com/opencv/opencv/archive/4.2.0.zip $ unzip 4.2.0.zip $ cd opencv-4.2.0
(4) cmake
WITH_GSTREAMER=ONに設定して、cmakeを実行します。(競合すると不安定との情報があったので、WITH_FFMPEGは、OFFとしました)
$ mkdir build && cd build $ cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -DINSTALL_PYTHON_EXAMPLES=ON -D INSTALL_C_EXAMPLES=ON -D PYTHON_EXECUTABLE=/usr/bin/python3 -D BUILD_EXAMPLES=ON -D WITH_GTK=ON -D WITH_GSTREAMER=ON -D WITH_FFMPEG=OFF -D WITH_QT=OFF ..
cmakeの出力で、Video I/OでGStreamerが有効になっていることを確認できます。
-- Video I/O: -- DC1394: NO -- GStreamer: YES (1.14.4) -- v4l/v4l2: YES (linux/videodev2.h)
(5) make & install
Raspberry Pi 4B や 3B+であれば、クアッドコアなので、とりあえず、make -j4で良いと思うのですが、途中で止まってしまうことがあったので、何回かやり直す(止まったところから再開できます)事になりました。
$ make -j4
途中で、止まると、オブジェクトファイルが中途半端に出来てしまって、次回、file not recognized: file truncated のエラーとなることがありました。
下記では、Mesh.cpp.oが壊れています。
CMakeFiles/example_tutorial_pnp_detection.dir/tutorial_code/calib3d/real_time_pose_estimation/src/Mesh.cpp.o: file not recognized: file truncated collect2: error: ld returned 1 exit status
このエラーは、当該オブジェクトファイルを消してしまって、再度、makeを実行する事で回避できます。
対応例)
$ find . -name Mesh.cpp.o ./samples/cpp/CMakeFiles/example_tutorial_pnp_detection.dir/tutorial_code/calib3d/real_time_pose_estimation/src/Mesh.cpp.o $ rm ./samples/cpp/CMakeFiles/example_tutorial_pnp_detection.dir/tutorial_code/calib3d/real_time_pose_estimation/src/Mesh.cpp.o
コンパイルは、なにやかんやで2時間ぐらいかかりました。終了したら、インストールします。
$ sudo make install
(6) 確認
動作を確認している様子です。Video I/0でGStreamerがYESになっていれば、OKです。
$ python Python 2.7.16 (default, Oct 10 2019, 22:02:15) [GCC 8.3.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import cv2 >>> print(cv2.getBuildInformation()) General configuration for OpenCV 4.2.0 ===================================== Version control: unknown (略) Video I/O: DC1394: NO GStreamer: YES (1.14.4) v4l/v4l2: YES (linux/videodev2.h)
4 appsink
GStreamerで、テスト入力をウインドウに表示する場合のコマンドです。
$ gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink
※ videoconvertは、上記の場合、必須ではありませんが、appsinkに送るために必要となります。比較しやすいように、敢えてvideoconvertを入れています
GStreamerで出力先となっているautovideosinkをappsinkに変更することで、OpenCVの入力ソースとして扱うことが出来ます。
下記は、OpenCVで、その入力をウインドウに表示しているコードです。
import cv2 src = 'videotestsrc ! videoconvert ! appsink' cap = cv2.VideoCapture(src) while(cap.isOpened()): ret, frame = cap.read() if frame is None: break cv2.imshow('frame',frame) cv2.waitKey(1) cap.release() cv2.destroyAllWindows()
ウインドウのタイトルがframeとなっており、OpenCVで表示されていることが分かります。
5 appsrc
v4l2srcをソースとして取得したWevカメラの映像をTCPストリームで送信する場合、GStreamerのコマンドラインは、以下のようになります。
$ gst-launch-1.0 v4l2src ! gdppay ! tcpserversink host={ホスト側のアドレス}
参考:[Kinesis Video Streams] Raspberry PiからGStreamerを使用してTCPストリーム配信してみました。
OpenCVのソースでGStreamerを有効にした場合、この v4l2src を単純に appsrc に置き換えることで、OpenCVのcv2.VideoWriter()からの入力を受け渡すことが可能になります。
sink = 'appsrc ! gdppay ! tcpserversink host=10.0.0.10'
Raspberry Pi上のOpenCVからGstreamerでTCPストリームのサーバを作成し、Macから確認してみました。
Raspberry Pi側のコードは、以下のとおりです。
RasPi
import cv2 cap = cv2.VideoCapture(0) cap.set(3,320) # WIDTH cap.set(4,240) # HEIGHT cap.set(1,15) # FPS framerate = 15/1 sink = 'appsrc ! gdppay ! tcpserversink host=10.0.0.10' out = cv2.VideoWriter(sink, 0, framerate, (320, 240)) while( cap.isOpened() ): ret, frame = cap.read() if frame is None: break out.write(frame) cv2.imshow('frame',frame) cv2.waitKey(1) cap.release() cv2.destroyAllWindows()
正常に起動すれば、4963でLISTENとなります。
$ netstat -an | grep 4953 tcp 0 0 10.0.0.10:4953 0.0.0.0:* LISTEN
これをMac側で受信して確認してみました。
Mac
$ gst-launch-1.0 -v tcpclientsrc host=10.0.0.10 ! gdpdepay ! videoconvert ! autovideosink sync=false
※sync=falseを付けないと、OpenCVの処理によって、データ待ちが発生して止まってしまう。
左が、RaspberryPiの画面で、右が、Mac上の画面です。
6 最後に
今回は、GStreamerのエレメントとして、OpenCVの入出力を利用できるようにしてみました。 これで、OpenCVを使用した画像処理を、Kinesis Video Streamsと統合できるような気がしてます。